Reset contributed in BreakerState::reset_line - #854
Merged
nicoburns merged 2 commits intoSep 29, 2026
Merged
Conversation
DJMcNab
reviewed
Sep 28, 2026
DJMcNab
left a comment
Member
There was a problem hiding this comment.
Reset applying the strut also doesn't read great here.
That is, I think reset_line should be the caller of add_strut (although I'm also not entirely convinced by add_strut; it seems like the strut should arise naturally from adding a box from a style - especially if lines with only inline boxes don't want the struts anyway...)
DJMcNab
approved these changes
Sep 29, 2026
staging-devin-ai-integration
Bot
force-pushed
the
devin/1790619679-reset-line-buffers
branch
from
September 29, 2026 12:58
e614d8f to
1c869cb
Compare
Collaborator
Author
FWIW, I believe that lines with only inline boxes do want the strut |
Member
Hmm, I thought this was the logic about lines with absolutely no glyphs. I guess I'm still confused about that change! |
nicoburns
added a commit
to DioxusLabs/parley
that referenced
this pull request
Oct 2, 2026
LLM Contributions: Generated with Fable 5.1 Low Depends on: - linebender#854 ## Context This is a follow-up to linebender#766 (`vertical-align`) that gets us back to performance parity with before that PR was merged. It was purposefully left as a follow-up to make things easier to review, but we probably need to land this or some alternative performance fix before we can release. ## Summary Eliminate `SmallVec<[SubtreeExtents; 2]>` on `LineBoxMetrics`, allowing it to impl `Copy` which is performance-critical because it is copied at every line breaking opportunity. ## New design - `LineBoxMetrics` loses the general ``SmallVec<[SubtreeExtents; 2]>`` and gains `root: SubtreeExtents`. So it still stores metrics for the root subtree on the line but extra subtrees generated by `vertical-align: top | bottom` move out. - `LineBoxMetrics` also gains `non_root_height: f32`, the current height of the tallest of those extra subtrees. This saves `line_height()` from having to compute it (which would otherwise be much more expensive in this new design). It is copied into saved break opportunities restored when reverting to one. - `BreakerState` gains `subtrees: SubtreeHistory` for the extra subtrees. This is per-line state that is processed in `finish_line` and then reset for the next line. All of the bookkeeping for it is inside `SubtreeHistory`: - It contains a `Vec<SubtreeExtents>` in which the current extents of a subtree are the *last* entry with its root. - `grow()` adds a box to a subtree. If the subtree's most recent entry in the `Vec` is frozen (see below) it pushes the extents as a new entry, otherwise it overwrites the entry in place. So entries only pile up when there are line-breaking opportunities between them. - `save()` is called at each line-breaking opportunity. It returns the current length, and marks every entry pushed so far as frozen (so they can safely be restored when taking a line-breaking opportunity) - `restore(save)` is called when taking a line-breaking opportunity. It truncates to the saved length, which drops everything pushed since. The entries before the save point are guaranteed to be unmodified as they were frozen when the save point was saved. - `current()` collects the latest entry for each root into a `SmallVec` once per-line in `finish_line`. - `PrevBoundaryState` (saved state for each line-break opportunity) gains `subtrees_save: usize`, the value returned by `save()` at that opportunity. ## Performance This is -25% on our existing line-breaking benchmarks. The below are stress tests with hundreds of `vertical-align: top` spans on a single line, each containing inline boxes of increasing height: | Case | This PR | | --- | ---: | | nowrap, 2048 spans, no boxes | 0.99× | | nowrap, 2048 spans × 8 boxes | 0.34× | | wrap, 2048 spans, no boxes | 0.51× | | wrap, 2048 spans × 8 boxes | 0.45× | | nowrap, 512 spans, 8192 boxes in the first | 1.18× | | wrap, 512 spans, 8192 boxes in the first | 5.85× | As you can see, there is a pathological case. That *may* be fixable, but at the cost of quite a big of complexity and slightly slower performance in the common cases. I've judged it not worth it for now. **Changelog**: None (performance improvement for never-published regression)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LLM Contributions: Generated with Opus 5.5 High. Reviewed by me.
This is a small refactor motivated by code quality. Previously an
&mutreference tocontributedwas being passed intoLineBoxMetrics'sresetmethod. This is a violation of separation of concerns. Resetting contributed now happens in thereset_linemethod ofBreakerStatewhich actually ownscontributed.Changelog: None (pure refactor)